package cn.xo68.boot.jwt;

import cn.xo68.core.crypto.EncryptConstants;
import cn.xo68.core.crypto.EncryptHandle;
import cn.xo68.core.crypto.EncryptSchemaEnums;
import cn.xo68.core.crypto.rsa.RsaEncryptHandleImpl;
import cn.xo68.core.date.DateTime;
import cn.xo68.core.security.Base64Utils;
import cn.xo68.core.util.StringTools;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;

/**
 * jwt Token 工具集
 * @author wuyy
 *@version 2019-12-2
 */
@Slf4j
public class JwtTokenUtil implements Serializable {

    private static final long serialVersionUID = -3301605591108950415L;

    public static final String CLAIM_KEY_USERNAME = Claims.SUBJECT;
    public static final String CLAIM_KEY_CREATED = "created";

    private final JwtTokenProperties jwtTokenProperties;

    private final EncryptHandle encryptHandle;

    public JwtTokenUtil(JwtTokenProperties jwtTokenProperties, EncryptHandle encryptHandle) {
        this.jwtTokenProperties = jwtTokenProperties;
        this.encryptHandle = encryptHandle;
    }

    /**
     * 根据令牌返回用户名称
     * @param token
     * @return
     */
	public String getUsernameFromToken(String token) {
        String username;
        try {
            final Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 返回令牌创建时间
     * @param token
     * @return
     */
    public Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            final Claims claims = getClaimsFromToken(token);
            created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
        } catch (Exception e) {
            created = null;
        }
        return created;
    }

    /**
     * 返回过期时间
     * @param token
     * @return
     */
    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    /**
     * 构建签名密钥
     * @param mode
     * @return
     */
    private Key buildKey(int mode) throws UnsupportedEncodingException {
        if(EncryptConstants.TYPE_RSA.equals(encryptHandle.getCryptoType()) && encryptHandle instanceof RsaEncryptHandleImpl){
            if(mode==Cipher.ENCRYPT_MODE){
                return encryptHandle.buildKey(mode, Base64Utils.decode(jwtTokenProperties.getPrivateSecret(),jwtTokenProperties.getEncoding()), EncryptSchemaEnums.PRIVATEENCRYPT);
            }else {
                return encryptHandle.buildKey(mode,Base64Utils.decode(jwtTokenProperties.getPublicSecret(),jwtTokenProperties.getEncoding()), EncryptSchemaEnums.PRIVATEENCRYPT);
            }
        }else {
            return encryptHandle.buildKey(mode,jwtTokenProperties.getPrivateSecret().getBytes(jwtTokenProperties.getEncoding()), EncryptSchemaEnums.PRIVATEENCRYPT);
        }

    }

    /**
     * 返回jwt的权利对象
     * @param token
     * @return
     */
    public Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(buildKey(Cipher.DECRYPT_MODE))
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    /**
     * 返回权利中对应键的值
     * @param token
     * @param key
     * @return
     */
    public Object getValueFromToken(String token,String key) {
    	Object val;
        try {
            final Claims claims = getClaimsFromToken(token);
            val = claims.get(key);
        } catch (Exception e) {
        	val = null;
        }
        return val;
    }

    /**
     * 返回jwt权利集合
     * @param token
     * @return
     */
    public Map<String,Object> getMapFromToken(String token) {
    	Map<String,Object> val;
        try {
            final Claims claims = getClaimsFromToken(token);
            val = claims;
        } catch (Exception e) {
        	val = null;
        }
        return val;
    }

    private Date generateExpirationDate(Long tokenExpiration) {
    	if(tokenExpiration==null || tokenExpiration<=0) tokenExpiration=jwtTokenProperties.getExpiration();
        return new Date(System.currentTimeMillis() + tokenExpiration * 1000);
    }

    /**
     * 是否过期
     * @param token
     * @return
     */
    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return (lastPasswordReset != null && created.before(lastPasswordReset));
    }

    /**
     * 生成令牌
     * @param userName
     * @return
     */
    public String generateToken(String userName) throws UnsupportedEncodingException {
    	return generateToken(userName,null,0L,null);
    }
    public String generateToken(String userName,String jtl,Long tokenExpiration,Map<String,Object> moreInfo) throws UnsupportedEncodingException {
        Map<String, Object> claims = new HashMap<>();
        if(StringTools.isNotEmpty(jtl)){
            claims.put(Claims.ID, jtl);
        }
        if(StringTools.isNotEmpty(userName)){
            claims.put(CLAIM_KEY_USERNAME, userName);
        }

        claims.put(CLAIM_KEY_CREATED, new Date());
        if(moreInfo!=null) {
        	claims.putAll(moreInfo);
        }
        return generateToken(claims,tokenExpiration);
    }

    private String generateToken(Map<String, Object> claims,Long tokenExpiration) throws UnsupportedEncodingException {
        SignatureAlgorithm alg=SignatureAlgorithm.HS512;
        if(EncryptConstants.TYPE_RSA.equals(encryptHandle.getCryptoType()) && encryptHandle instanceof RsaEncryptHandleImpl){
            switch (((RsaEncryptHandleImpl) encryptHandle).getKeySize()){
                case 512:
                    alg=SignatureAlgorithm.RS256;
                    break;
                case 1024:
                    alg=SignatureAlgorithm.RS384;
                    break;
                case 2048:
                    alg=SignatureAlgorithm.RS512;
                    break;
                default:
                    throw new JwtException("unsupported");
            }

        }

        return Jwts.builder()
                .setClaims(claims)
                .setIssuer(jwtTokenProperties.getIssuer())
                .setIssuedAt(DateTime.Now().getDate())
                .setExpiration(generateExpirationDate(tokenExpiration))
                .signWith(alg, buildKey(Cipher.ENCRYPT_MODE))
                .compact();
    }

    /**
     * 是否能被刷新
     * @param token
     * @param lastPasswordReset
     * @return
     */
    public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
        final Date created = getCreatedDateFromToken(token);
        return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
                && !isTokenExpired(token);
    }

    /**
     * 生成刷新令牌
     * @param token
     * @param tokenExpiration
     * @return
     */
    public String refreshToken(String token,Long tokenExpiration) {
        String refreshedToken;
        try {
            final Claims claims = getClaimsFromToken(token);
            claims.put(CLAIM_KEY_CREATED, new Date());
            refreshedToken = generateToken(claims,tokenExpiration);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * 验证令牌 过期时间，用户名称，密码设置时间
     * @param token
     * @param userName
     * @param lastPasswordResetDate
     * @return
     */
    public Boolean validateToken(String token, String userName,Date lastPasswordResetDate) {
        final String username = getUsernameFromToken(token);
        final Date created = getCreatedDateFromToken(token);
        return (
                username.equals(userName)
                        && !isTokenExpired(token)
                        && !isCreatedBeforeLastPasswordReset(created, lastPasswordResetDate));
    }

    /**
     * 验证令牌过期时间
     * @param token
     * @return
     */
    public Boolean validateToken(String token) {
        return !isTokenExpired(token);
    }
}